jqkit.net = {};

jqkit.net.cookies = {};

/**
 * Static constant for the size of cookies. Per the spec, there's a 4K limit to the size of a cookie. 
 * To make sure users can't break this limit, we should truncate long cookies at 3950 bytes, to be extra 
 * careful with dumb browsers/proxies that interpret 4K as 4000 rather than 4096.
 * @type {number}
 */
jqkit.net.cookies.MAX_COOKIE_LENGTH = 3950;

/**
 * Returns true if cookies are enabled.
 * @return {boolean} True if cookies are enabled.
 */
jqkit.net.cookies.areEnabled = function() {
	var cookieName = 'COOKIES_TEST_';
	jqkit.net.cookies.set(cookieName, '1');
	if (!jqkit.net.cookies.get(cookieName)) {
		return false;
	}
	jqkit.net.cookies.remove(cookieName);
	return true;
};

/**
 * Do not allow '=', ';', or white space in the name.
 *
 * NOTE: The following are allowed by this method, but should be avoided for cookies handled by the server.
 * - any name starting with '$'
 * - 'Comment'
 * - 'Domain'
 * - 'Expires'
 * - 'Max-Age'
 * - 'Path'
 * - 'Secure'
 * - 'Version'
 *
 * @param {string} name Cookie name.
 * @return {boolean} Whether name is valid.
 */
jqkit.net.cookies.isValidName = function(name) {
	return !(/[;=\s]/.test(name));
};

/**
 * We do not allow ';' or line break in the value.
 * Spec does not mention any illegal characters, but in practice semi-colons break parsing and line breaks 
 * truncate the name.
 * @param {string} value Cookie value.
 * @return {boolean} Whether value is valid.
 */
jqkit.net.cookies.isValidValue = function(value) {
	return !(/[;\r\n]/.test(value));
};

/**
 * Sets a cookie. The max_age can be -1 to set a session cookie. To remove and expire cookies, use remove() instead.
 *
 * Neither the name nor the value are encoded in any way. It is up to the callers of get and set (as well as all 
 * the other methods) to handle any possible encoding and decoding.
 *
 * @param {string} name The cookie name.
 * @param {string} value The cookie value.
 * @param {number} opt_maxAge The max age in seconds (from now). Use -1 to set a session cookie. If not provided, the 
 * default is -1 (a session cookie).
 * @param {string} opt_path The path of the cookie. If not present then this uses the full request path.
 * @param {string} opt_domain The domain of the cookie, or null to not specify a domain attribute (browser will use 
 * the full request host name).
 * @param {boolean} opt_secure Whether the cookie should only be sent over a secure channel.
 */
jqkit.net.cookies.set = function(name, value, opt_maxAge, opt_path, opt_domain, opt_secure) {
	if (jqkit.net.cookies.isValidName(name) === false) {
		throw 'Invalid cookie name "' + name + '"';
		return;
	}

	if (jqkit.net.cookies.isValidValue(value) === false) {
		throw 'Invalid cookie value "' + value + '"';
		return;
	}

	if (jqkit.isDefinedAndNotNull(opt_maxAge) === false) {
		opt_maxAge = -1;
	}

	var domainStr = opt_domain ? ';domain=' + opt_domain : '';
	var pathStr = opt_path ? ';path=' + opt_path : '';
	var secureStr = opt_secure ? ';secure' : '';

	var expiresStr;
	if (opt_maxAge < 0) {
	    // Case 1: Set a session cookie.
		expiresStr = '';
	} else if (opt_maxAge === 0) {
	    // Case 2: Expire the cookie.
		// Note: Don't use Jan 1, 1970 for date because NS 4.76 will try to convert it to local time, and if 
        // the local time is before Jan 1, 1970, then the browser will ignore the Expires attribute altogether.
		var pastDate = new Date(1970, 1, 1);  // Feb 1, 1970
		expiresStr = ';expires=' + pastDate.toUTCString();
	} else {
	    // Case 3: Set a persistent cookie.
		var futureDate = new Date((new Date()).getTime() + opt_maxAge * 1000);
		expiresStr = ';expires=' + futureDate.toUTCString();
	}
	
	document.cookie = name + '=' + value + domainStr + pathStr + expiresStr + secureStr;
};

/**
 * Returns the value for the first cookie with the given name.
 * @param {string} name The name of the cookie to get.
 * @param {string} opt_default If not found this is returned instead.
 * @return {string|undefined} The value of the cookie. If no cookie is set this returns opt_default or undefined 
 * if opt_default is not provided.
 */
jqkit.net.cookies.get = function(name, opt_default) {
	var nameEq = name + '=';
	var parts = jqkit.net.cookies.getParts_();
	for (var i = 0, part; part = parts[i]; i++) {
		if (part.indexOf(nameEq) === 0) {
			return part.substr(nameEq.length);
		}
	}
	return opt_default;
};

/**
 * Removes and expires a cookie.
 * @param {string} name The cookie name.
 * @param {string} opt_path The path of the cookie, or null to expire a cookie set at the full request path. If not provided, 
 * the default is '/' (i.e. path=/).
 * @param {string} opt_domain The domain of the cookie, or null to expire a cookie set at the full request host name. If not, 
 * the default is null (i.e. cookie at full request host name).
 * @return {boolean} Whether the cookie existed before it was removed.
 */
jqkit.net.cookies.remove = function(name, opt_path, opt_domain) {
	var exists = jqkit.net.cookies.containsKey(name);
	jqkit.net.cookies.set(name, '', 0, opt_path, opt_domain);
	return exists;
};

/**
 * Gets the names for all the cookies.
 * @return {Array<string>} An array with the names of the cookies.
 */
jqkit.net.cookies.getKeys = function() {
	return jqkit.net.cookies.getKeyValues_().keys;
};

/**
 * Gets the values for all the cookies.
 * @return {Array<string>} An array with the values of the cookies.
 */
jqkit.net.cookies.getValues = function() {
	return jqkit.net.cookies.getKeyValues_().values;
};

/**
 * @return {number} The number of cookies for this document.
 */
jqkit.net.cookies.getCount = function() {
	return jqkit.net.cookies.getParts_().length;
};

/**
 * Returns whether there is a cookie with the given name.
 * @param {string} key The name of the cookie to test for.
 * @return {boolean} Whether there is a cookie by that name.
 */
jqkit.net.cookies.containsKey = function(key) {
	return jqkit.isDefined(jqkit.net.cookies.get(key));
};

/**
 * Returns whether there is a cookie with the given value.
 * @param {string} value The value to check for.
 * @return {boolean} Whether there is a cookie with that value.
 */
jqkit.net.cookies.containsValue = function(value) {
	var values = jqkit.net.cookies.getKeyValues_().values;
	for (var i = 0, length = values.length; i < length; i++) {
		if (values[i] == value) {
			return true;
		}
	}
	return false;
};

/**
 * Removes all cookies for this document. Note that this will only remove cookies from the current path and domain.  
 * If there are cookies set using a subpath and/or another domain these will still be there.
 */
jqkit.net.cookies.clear = function() {
	var keys = jqkit.net.cookies.getKeyValues_().keys;
	for (var i = keys.length - 1; i >= 0; i--) {
		jqkit.net.cookies.remove(keys[i]);
	}
};

/**
 * @return {array<string>} The cookie split on semi colons.
 * @private
 */
jqkit.net.cookies.getParts_ = function() {
	return (document.cookie || '').split(/\s*;\s*/);
};

/**
 * Gets the names and values for all the cookies.
 * @return {Object} An object with keys and values.
 */
jqkit.net.cookies.getKeyValues_ = function() {
	var parts = jqkit.net.cookies.getParts_();
	var keys = [], values = [], index, part;
	for (var i = 0; part = parts[i]; i++) {
		index = part.indexOf('=');
		if (index === -1) { // empty name
			keys.push('');
			values.push(part);
		} else {
			keys.push(part.substring(0, index));
			values.push(part.substring(index + 1));
		}
	}
	return { keys: keys, values: values };
};